package com.hboxs.asl.controller.wechat;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.hboxs.asl.JsonMessage;
import com.hboxs.asl.controller.asl.BaseController;
import com.hboxs.asl.entity.Member;
import com.hboxs.asl.entity.Order;
import com.hboxs.asl.service.MemberService;
import com.hboxs.asl.service.OrderService;
import com.hboxs.asl.wechat.entity.WxOrder;
import com.hboxs.asl.wechat.payment.base.WxPayApi;
import com.hboxs.asl.wechat.payment.base.WxPayConfig;
import com.hboxs.asl.wechat.payment.base.WxPayData;
import com.hboxs.asl.wechat.payment.base.WxPayException;
import com.hboxs.asl.wechat.service.WxConfigService;
import com.hboxs.asl.wechat.service.WxOrderService;
import com.hboxs.asl.wechat.utils.HttpClientUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.log4j.Logger;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by study on 7/30/2015.
 */
@Controller("jsApiPayController")
@RequestMapping("/wechat/payment/jsapipay")
public class JsApiPayController extends BaseController {
    protected Logger logger = Logger.getLogger(getClass());

    @Resource(name = "wxConfigServiceImpl")
    private WxConfigService wxConfigService;
    @Resource(name = "wxOrderServiceImpl")
    private WxOrderService wxOrderService;
    @Resource(name = "orderServiceImpl")
    private OrderService orderService;
    @Resource(name = "memberServiceImpl")
    private MemberService memberService;

    private ObjectMapper mapper = new ObjectMapper();

    private SimpleDateFormat dateFormat = new SimpleDateFormat();

    private String access_token;
    private String openid;

    private WxPayData unifiedOrderResult;

    {
        dateFormat.applyPattern("yyyyMMddHHmmss");
    }

    /**
     * JS-SDK 配置文件
     *
     * @return
     */
    @RequestMapping(value = "/jssdk-config", method = RequestMethod.GET)
    public
    @ResponseBody
    Object jssdkConfig(String url) {
        // 这个地方要注意，参与签名的是网页授权获取用户信息时微信后台回传的完整url
        if (StringUtils.isEmpty(url)) {
            return JsonMessage.error("页面链接为空！");
        }

        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return JsonMessage.error("链接解码失败！");
        }

        int index = url.indexOf("#");
        if (index > 0) {
            url = url.substring(0, index);
            logger.debug("New URL:" + url);
        }

        WxPayData data = new WxPayData();
        data.setValue("timestamp", WxPayApi.generateTimeStampSeconds());
        data.setValue("noncestr", WxPayApi.generateNonceStr());
        data.setValue("jsapi_ticket", wxConfigService.latestJsapiTicket());

        data.setValue("url", url);

        try {
            data.setValue("signature", data.makeSha1Sign());
            data.setValue("appId", WxPayConfig.APPID);
            data.remove("jsapi_ticket");
            data.remove("url");
            logger.info(data.getValues());
            return JsonMessage.success(data.getValues());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return JsonMessage.error("获取JSSDK配置错误！");
    }

    /**
     * 订单支付
     *
     * @return
     */
    @RequestMapping(value = "/pay", method = RequestMethod.POST)
    public
    @ResponseBody
    Object deposit(String sn) {
        // JSAPI支付预处理
        try {
            Member member = memberService.getCurrent();
            if (member == null) {
                return JsonMessage.USER_NOT_LOGIN;
            }
            // 2. 查找订单
            Order order = orderService.findBySn(sn);
            if (order == null) {
                return JsonMessage.error("订单不存在!");
            }

            // 3. 判断订单是否属于用户
            if (!member.equals(order.getMember())) {
                return JsonMessage.error("订单不属于本人！");
            }

            String body = "订单:" + order.getSn();
            String attach = "Order:"+sn;
            int total_fee = order.getAmountPayable().multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP).intValue();
            String goods_tag = "商品";
            String openid = memberService.getCurrent().getWechat().getWxUserInfo().getOpenid();
            getUnifiedOrderResult(body, attach, openid, total_fee, goods_tag, order);
            String wxJsApiParam = getJsApiParameters(); // 获取H5调起JS API参数
            return JsonMessage.success(wxJsApiParam);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return JsonMessage.error("获取支付失败！");
    }

    /**
     * 充值
     *
     * @return
     */
    @RequestMapping(value = "/recharge", method = RequestMethod.POST)
    public
    @ResponseBody
    JsonMessage recharge(BigDecimal quota) {
        try {
            String openid = memberService.getCurrent().getUsername();
            if (org.apache.commons.lang.StringUtils.isEmpty(openid)) {
                return JsonMessage.error("用户未登录！");
            }

            String attach="RECHARGE";
            getUnifiedOrderResult("易经-充值金额", attach, openid, (int) (quota.doubleValue() * 100), "充值金", null);
            String wxJsApiParam = getJsApiParameters(); // 获取H5调起JS API参数
            return JsonMessage.success(wxJsApiParam);
        } catch (WxPayException e) {
            e.printStackTrace();
        }
        return JsonMessage.error("获取支付失败！");
    }

    /**
     * 调用统一下单，获得下单结果
     *
     * @param body      商品描述
     * @param attach    附加数据
     * @param openid    用户
     * @param total_fee 总金额
     * @param goods_tag 商品标记
     * @return 统一下单结
     * @throws WxPayException 失败时抛异常
     */
    public WxPayData getUnifiedOrderResult(String body, String attach, String openid, int total_fee, String goods_tag, Order order) throws WxPayException {
        // 统一下单
        WxPayData data = new WxPayData();
        data.setValue("body", body);
        data.setValue("attach", attach);
        data.setValue("out_trade_no", WxPayApi.generateOutTradeNo());
        data.setValue("total_fee", total_fee);
        Date date = new Date();
        data.setValue("time_start", dateFormat.format(date));
        // 延迟10分钟
        date.setTime(date.getTime() + (10 * 60 * 1000));
        data.setValue("time_expire", dateFormat.format(date));
        data.setValue("goods_tag", goods_tag);
        data.setValue("trade_type", "JSAPI");
        data.setValue("openid", openid);
        WxPayData result = WxPayApi.unifiedOrder(data);

        if (!result.isSet("appid") || StringUtils.isEmpty(result.getValue("prepay_id"))) {
            logger.info(!result.isSet("appid"));
            logger.info(StringUtils.isEmpty(result.getValue("prepay_id")));

            logger.error("UnifiedOrder response fail!");
            throw new WxPayException("UnifiedOrder response fail!");
        }

        // 保存订单
        try {
            WxOrder wxOrder = new WxOrder();
            wxOrder.setAttach(attach);
            wxOrder.setBody(body);
            wxOrder.setGoodsTag(goods_tag);
            wxOrder.setOutTradeNo(data.getValue("out_trade_no").toString());
            wxOrder.setTimeStart(new Timestamp(dateFormat.parse(data.getValue("time_start").toString()).getTime()));
            wxOrder.setTotalFee(new BigDecimal(total_fee).divide(new BigDecimal(100)));

            wxOrderService.save(wxOrder);

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

        unifiedOrderResult = result;
        return result;
    }

    /**
     * 从统一下单成功返回的数据中获取微信浏览器调起jsapi支付所需的参数， 微信浏览器调起JSAPI时的输入参数格式如下：
     * {
     * "appId" : "wx2421b1c4370ec43b", //公众号名称，由商户传入
     * "timeStamp":" 1395712654", //时间戳，自1970年以来的秒数
     * "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串
     * "package" : "prepay_id=u802345jgfjsdfgsdg888",
     * "signType" : "MD5", //微信签名方式:
     * "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 }
     *
     * @return String 微信浏览器调起JSAPI时的输入参数，json格式可以直接做参数用
     * 更详细的说明请参考网页端调起支付API：http:
     * //pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7
     * @throws WxPayException 出错抛异常
     */
    private String getJsApiParameters() throws WxPayException {
        logger.debug("JsApiPay::GetJsApiParam is processing...");

        WxPayData jsApiParam = new WxPayData();
        jsApiParam.setValue("appId", unifiedOrderResult.getValue("appid"));
        jsApiParam.setValue("timeStamp", WxPayApi.generateTimeStamp());
        jsApiParam.setValue("nonceStr", WxPayApi.generateNonceStr());
        jsApiParam.setValue("package", "prepay_id=" + unifiedOrderResult.getValue("prepay_id"));
        jsApiParam.setValue("signType", "MD5");
        jsApiParam.setValue("paySign", jsApiParam.makeSign());

        String parameters = "";
        try {
            parameters = mapper.writeValueAsString(jsApiParam.getValues());
        } catch (Exception e) {
            throw new WxPayException(e.getMessage());
        }

        logger.info("Get jsApiParam : " + parameters);
        return parameters;
    }

    /**
     * 网页授权获取用户基本信息的全部过程 详情请参看网页授权获取用户基本信息：http://mp.weixin.qq.com/wiki/17/
     * c0f37d5704f0b64713d5d2c37b468d75.html
     * 第一步：利用url跳转获取code
     * 第二步：利用code去获取openid和access_token
     */
    private String getOpenidAndAccessToken(HttpServletRequest request, HttpServletResponse response) throws WxPayException {
        if (!org.apache.commons.lang.StringUtils.isEmpty(request.getParameter("code"))) {
            // 获取code码，以获取openid和access_token
            String code = request.getParameter("code");
            logger.debug("Get code : " + code);
            getOpenidAndAccessTokenFromCode(code);
            return null;
        } else {
            // 构造网页授权获取code的URL
            String requestUrl = request.getRequestURL().toString();

            String redirect_uri = "";
            try {
                redirect_uri = URLEncoder.encode(requestUrl, "Utf-8");
            } catch (Exception e) {
                throw new WxPayException(e.getMessage());
            }
            WxPayData data = new WxPayData();
            data.setValue("appid", WxPayConfig.APPID);
            data.setValue("redirect_uri", redirect_uri);
            data.setValue("response_type", "code");
            data.setValue("scope", "snsapi_base");
            data.setValue("state", "STATE" + "#wechat_redirect");
            String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" + data.toUrl();
            logger.debug("Will Redirect to URL : " + url);
            return url;
        }
    }

    /**
     * 通过code换取网页授权access_token和openid的返回数据，正确时返回的JSON数据包如下：
     * {
     * "access_token":"ACCESS_TOKEN", "expires_in":7200,
     * "refresh_token":"REFRESH_TOKEN", "openid":"OPENID", "scope":"SCOPE",
     * "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
     * }
     * 其中access_token可用于获取共享收货地址
     * openid是微信支付jsapi支付接口统一下单时必须的参数
     * 更详细的说明请参考网页授权获取用户基本信息：http://mp.weixin.qq.com
     * /wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
     *
     * @失败时抛异常WxPayException
     */
    private void getOpenidAndAccessTokenFromCode(String code) throws WxPayException {
        try {
            // 构造获取openid及access_token的url
            WxPayData data = new WxPayData();
            data.setValue("appid", WxPayConfig.APPID);
            data.setValue("secret", WxPayConfig.APPSECRET);
            data.setValue("code", code);
            data.setValue("grant_type", "authorization_code");
            String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" + data.toUrl();

            // 请求url以获取数据
            JSONObject result = HttpClientUtils.get(url);

            logger.debug("GetOpenidAndAccessTokenFromCode response : " + result);

            if (result.has("access_token"))
                access_token = result.getString("access_token");
            if (result.has("openid"))
                openid = result.getString("openid");

            logger.debug("Get openid : " + openid);
            logger.debug("Get access_token : " + access_token);
        } catch (Exception ex) {
            logger.error(ex.getMessage());
            throw new WxPayException(ex.getMessage());
        }
    }


    /**
     * 获取收货地址js函数入口参数,详情请参考收货地址共享接口：http://pay.weixin.qq.com/wiki/doc/api/jsapi.
     * php?chapter=7_9
     *
     * @return 共享收货地址js函数需要的参数，json格式可以直接做参数使用
     */
    private String getEditAddressParameters(HttpServletRequest request) throws WxPayException {
        String parameter = "";
        try {
            // 这个地方要注意，参与签名的是网页授权获取用户信息时微信后台回传的完整url
            String url = request.getRequestURL().toString();
            if (!StringUtils.isEmpty(request.getQueryString())) {
                url += "?" + request.getQueryString();
            }

            // 构造需要用SHA1算法加密的数据
            WxPayData signData = new WxPayData();
            signData.setValue("appid", WxPayConfig.APPID);
            signData.setValue("url", url);
            signData.setValue("timestamp", WxPayApi.generateTimeStamp());
            signData.setValue("noncestr", WxPayApi.generateNonceStr());
            signData.setValue("accesstoken", access_token);
            String param = signData.toUrl();

            logger.debug("SHA1 encrypt param : " + param);
            String addrSign = DigestUtils.shaHex(param);
            logger.debug("SHA1 encrypt result : " + addrSign);

            // 获取收货地址js函数入口参数
            WxPayData afterData = new WxPayData();
            afterData.setValue("appId", WxPayConfig.APPID);
            afterData.setValue("scope", "jsapi_address");
            afterData.setValue("signType", "sha1");
            afterData.setValue("addrSign", addrSign);
            afterData.setValue("timeStamp", signData.getValue("timestamp"));
            afterData.setValue("nonceStr", signData.getValue("noncestr"));

            // 转为json格式
            parameter = mapper.writeValueAsString(afterData.getValues());
            logger.debug("Get EditAddressParam : " + parameter);
        } catch (Exception ex) {
            logger.error(ex.toString());
            throw new WxPayException(ex.toString());
        }

        return parameter;
    }
}
